`timescale 1ns/1ps
`default_nettype none

//`define DEBUG

/* NOTE:
* - 帧率同步模块
*/

module cxy_fps_sync
#(parameter
    MAX_PHASE_ERROR = 2000
) (
    // system signal
    input  wire         I_sclk,  // 125M
    input  wire         I_rst_n,
    // config
    input  wire         I_cfg_output_enable,    // 输出使能
    input  wire         I_cfg_fps_sync_en,      // 帧率同步使能
    input  wire [15:0]  I_cfg_chain_cycle,      // 串移时钟数
    input  wire [15:0]  I_cfg_min_chain,        // 最小串移周期数
    input  wire [11:0]  I_cfg_chain_num,        // 显示周期串移数
    // frame
    input  wire         I_frame_sync,
    // display control
    output wire         O_display_reset,        // 强制重新开始串移
    input  wire         I_display_ready,        // 输出模块ready
    input  wire         I_display_end,          // 每个显示周期结束标识
    input  wire         I_display_chain_end,    // 每个串移周期结束标识
    output wire         O_display_param_en,     // 更新参数
    output wire [15:0]  O_display_chain_cycle,  // 串移时钟数
    output wire [15:0]  O_display_extra_cycle   // 额外的串移时钟数
);
//------------------------Parameter----------------------
// fsm
localparam [2:0]
    IDLE  = 0,
    FREE  = 1,
    PREP  = 2,
    CALC  = 3,
    RESET = 4,
    SYNC  = 5;

localparam
    INIT_PHASE_DIFF = MAX_PHASE_ERROR,
    MAX_PHASE_DIFF  = MAX_PHASE_ERROR * 2;

//------------------------Local signal-------------------
// fsm
reg  [2:0]  state;

// prep
reg  [15:0] clock_cnt;
reg         clock_cnt_loop;
reg  [11:0] chain_cnt;
reg         chain_cnt_loop;
reg  [13:0] chain_num;

// calc
reg  [13:0] tmp_chain_max;
reg  [15:0] tmp_extra_cycle;
reg  [15:0] tmp_chain_cycle;

// sync
reg  [13:0] chain_per_frame;
reg         sync_lost;
reg  [12:0] phase_cnt;
reg         phase_detect;
reg         phase_error;
reg         exceed_min_chain;

// display control
reg         param_en;
reg  [15:0] chain_cycle;
reg  [15:0] extra_cycle;

// debug
reg  [6:0]  tick_cnt;
reg  [15:0] us_cnt;

//------------------------Instantiation------------------

//------------------------Body---------------------------
//{{{+++++++++++++++++++++fsm++++++++++++++++++++++++++++
//state[2:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        state <= IDLE;
    else if(!I_cfg_output_enable || !I_cfg_fps_sync_en || !I_display_ready)
        state <= IDLE;
    else
    case(state)
        IDLE:   state <= FREE;

        FREE:
            if(I_frame_sync)
                state <= PREP;

        PREP:
            if(I_frame_sync)
                state <= CALC;

        CALC:
            if(I_frame_sync)
            begin
                if(tmp_chain_cycle<I_cfg_min_chain)
                    state <= FREE;
                else
                    state <= RESET;
            end

        RESET:
            if(phase_cnt==INIT_PHASE_DIFF-2)
                state <= SYNC;

        SYNC:
            if(sync_lost || phase_error || exceed_min_chain)
                state <= IDLE;

        default:    state <= IDLE;
    endcase

//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++prep+++++++++++++++++++++++++++
// NOTE: 在PREP状态下计算出两帧之间的显示周期数/总串移数

//clock_cnt[15:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        clock_cnt <= 1'b1;
    else if(state==FREE)
        clock_cnt <= 1'b1;
    else if(state==PREP)
    begin
        if(clock_cnt_loop)
            clock_cnt <= 1'b1;
        else
            clock_cnt <= clock_cnt + 1'b1;
    end

//clock_cnt_loop
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        clock_cnt_loop <= 'b0;
    else if(state==PREP && clock_cnt==I_cfg_chain_cycle-1)
        clock_cnt_loop <= 1'b1;
    else
        clock_cnt_loop <= 'b0;

//chain_cnt[11:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        chain_cnt <= 1'b1;
    else if(state==FREE)
        chain_cnt <= 1'b1;
    else if(state==PREP && clock_cnt_loop)
    begin
        if(chain_cnt_loop)
            chain_cnt <= 1'b1;
        else
            chain_cnt <= chain_cnt + 1'b1;
    end

//chain_cnt_loop
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        chain_cnt_loop <= 'b0;
    else if(state==FREE)
        chain_cnt_loop <= 'b0;
    else if(state==PREP && clock_cnt_loop)
    begin
        if(chain_cnt==I_cfg_chain_num-1)
            chain_cnt_loop <= 1'b1;
        else
            chain_cnt_loop <= 'b0;
    end

//chain_num[13:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        chain_num <= 'b0;
    else if(state==FREE)
        chain_num <= 'b0;
    else if(state==PREP)
    begin
        if(I_frame_sync && chain_num==0)
            chain_num <= I_cfg_chain_num;
        else if(clock_cnt_loop && chain_cnt_loop)
            chain_num <= chain_num + I_cfg_chain_num;
    end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++calc+++++++++++++++++++++++++++
//tmp_chain_max[13:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        tmp_chain_max <= 'b0;
    else if(state==PREP && I_frame_sync)
        tmp_chain_max <= chain_num - 1'b1;

//tmp_extra_cycle[15:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        tmp_extra_cycle <= 'b0;
    else if(I_frame_sync)
        tmp_extra_cycle <= 1'b1;
    else if(tmp_extra_cycle==tmp_chain_max)
        tmp_extra_cycle <= 'b0;
    else
        tmp_extra_cycle <= tmp_extra_cycle + 1'b1;

//tmp_chain_cycle[15:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        tmp_chain_cycle <= 'b0;
    else if(I_frame_sync)
        tmp_chain_cycle <= 'b0;
    else if(tmp_extra_cycle==tmp_chain_max)
        tmp_chain_cycle <= tmp_chain_cycle + 1'b1;

//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++sync+++++++++++++++++++++++++++
//chain_per_frame[13:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        chain_per_frame <= 'b0;
    else if(state==CALC)
        chain_per_frame <= 'b0;
    else if(state==SYNC)
    begin
        if(I_frame_sync)
            chain_per_frame <= 'b0;
        else if(I_display_chain_end)
            chain_per_frame <= chain_per_frame + 1'b1;
    end

//sync_lost
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        sync_lost <= 'b0;
    else if(chain_per_frame=={14{1'b1}})
        sync_lost <= 1'b1;
`ifdef DEBUG
    else if(us_cnt=={16{1'b1}})
        sync_lost <= 1'b1;
`endif
    else
        sync_lost <= 'b0;

//phase_cnt[12:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        phase_cnt <= 1'b1;
    else if(I_frame_sync || (state==SYNC && I_display_end))
        phase_cnt <= 1'b1;
    else
        phase_cnt <= phase_cnt + 1'b1;

//phase_detect
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        phase_detect <= 'b0;
    else if(state==RESET)
        phase_detect <= 'b0;
    else if(state==SYNC)
    begin
        if(I_frame_sync)
            phase_detect <= 1'b1;
        else if(I_display_end)
            phase_detect <= 1'b0;
    end

//phase_error
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        phase_error <= 'b0;
    else if(phase_detect && phase_cnt>MAX_PHASE_DIFF)
        phase_error <= 1'b1;
    else
        phase_error <= 'b0;

//exceed_min_chain
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        exceed_min_chain <= 'b0;
    else if(I_frame_sync && tmp_chain_cycle<I_cfg_min_chain)
        exceed_min_chain <= 1'b1;
    else
        exceed_min_chain <= 'b0;

//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++display control++++++++++++++++
//assign O_display_reset     = (state==IDLE || state==RESET);
assign O_display_reset       = (state==RESET);
assign O_display_param_en    = param_en;
assign O_display_chain_cycle = chain_cycle;
assign O_display_extra_cycle = extra_cycle;

//param_en
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        param_en <= 'b0;
    else if(state==SYNC && I_frame_sync)
        param_en <= 1'b1;
    else if(I_display_end)
        param_en <= 'b0;

//chain_cycle[15:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        chain_cycle <= 'b0;
    else if(state==IDLE)
        chain_cycle <= I_cfg_chain_cycle;
    else if((state==CALC || state==SYNC) && I_frame_sync)
        chain_cycle <= tmp_chain_cycle;

//extra_cycle[15:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        extra_cycle <= 'b0;
    else if(state==IDLE)
        extra_cycle <= 'b0;
    else if((state==CALC || state==SYNC) && I_frame_sync)
        extra_cycle <= tmp_extra_cycle;
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//++++++++++++++++++++++++debug++++++++++++++++++++++++++
//tick_cnt[6:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        tick_cnt <= 'b0;
    else if(I_frame_sync || tick_cnt==124)
        tick_cnt <= 'b0;
    else
        tick_cnt <= tick_cnt + 1'b1;

//us_cnt[15:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        us_cnt <= 'b0;
    else if (I_frame_sync)
        us_cnt <= 'b0;
    else if(tick_cnt==124)
        us_cnt <= us_cnt + 1'b1;
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++

endmodule

`default_nettype wire

// vim:set ts=4 sw=4 et fenc=utf-8 fdm=marker:
